/*
 * @Version:@Date: 2022-04-19
 * @Descripttion: 封装 WebSocket（断线重连 ｜ 发布订阅 ｜ 超时提示 ｜ 心跳保活 ｜ 错误处理）
 * @LastEditTime: 2022-10-27 15:20:48
 */

import { ElMessage } from 'element-plus'
import {delay} from "@/utils/common";


/**
 * 回调函数类型定义
 */
export type Callback = (e: Event) => void
export type MessageCallback<RT> = (e: RT) => void

/**
 * 接口定义
 */
interface Ioptions<RT> {
    // 链接的通道的地址
    url: string | null

    // 是否需要心跳
    isHeart?: boolean

    // 心跳信息,默认为'ping'
    heartMsg?: string

    // 是否销毁
    isRestory?: boolean

    // 是否自动重连
    isReconnect?: boolean

    // 重连时间间隔
    reconnectTime?: number

    // 重连次数 -1 则不限制
    reconnectCount?: number

    // 连接成功的回调
    onopen?: Callback

    // 关闭的回调
    onclose?: Callback

    // 错误的回调
    onerror?: Callback

    // 消息的回调
    onmessage?: MessageCallback<RT>
}

/**
 * 心跳基类
 */
export class Heart {
    // 心跳时间间隔
    private heartTime = 5000

    // 心跳计时器
    private heartTimer!: any

    // 心跳计时器
    private serverHeartTimer!: any

    /**
     * 重置心跳
     */
    reset(): void {
        clearTimeout(this.heartTimer)
        clearTimeout(this.serverHeartTimer)
    }

    /**
     * 启动心跳
     * @param {Function} cb 回调函数
     */
    start(cb: Callback): void {
        this.heartTimer = setTimeout((e: Event) => {
            cb(e)

            this.serverHeartTimer = setTimeout((e: Event) => {
                cb(e)

                // 重新开始检测
                this.reset()
                this.start(cb)
            }, this.heartTime)
        }, this.heartTime)
    }
}

/**
 * Socket Class 类型
 */
export default class Socket<T, RT> extends Heart {
    // socket 实例
    private ws!: WebSocket

    // 重连计时器
    private reconnectTimer: any

    // 重连次数变量保存，防止丢失
    private reconnectCount: number = 5

    private options: Ioptions<RT> = {
        // 链接的通道的地址
        url: null,

        // 是否需要心跳
        isHeart: false,

        // 心跳信息,默认为'ping'
        heartMsg: 'ping',

        // 是否销毁
        isRestory: false,

        // 是否自动重连
        isReconnect: true,

        // 重连时间间隔
        reconnectTime: 5000,

        // 重连次数 -1 则不限制
        reconnectCount: 5,

        // 连接成功的回调
        onopen: (e: Event) => {
            console.log('连接成功的默认回调::::', e)
        },

        // 关闭的回调
        onclose: (e: Event) => {
            console.log('关闭的默认回调::::', e)
        },

        // 消息的回调
        onmessage: (e: RT) => {
            console.log('连接成功的默认回调::::', e)
        },

        // 错误的回调
        onerror: (e: Event) => {
            console.log('错误的默认回调::::', e)
        }
    }

    constructor(ops: Ioptions<RT>) {
        super()

        Object.assign(this.options, ops)

        this.create()
    }

    /**
     * 建立连接
     */
    private create(): void {

        if (!('WebSocket' in window)) {
            ElMessage.error("您的浏览器不支持websocket协议,建议使用新版谷歌、火狐等浏览器，请勿使用IE10以下浏览器");

            throw new Error('您的浏览器不支持websocket协议,建议使用新版谷歌、火狐等浏览器，请勿使用IE10以下浏览器')
        }

        if (!this.options.url) {
            ElMessage.error("地址不存在，无法建立通道");

            throw new Error('地址不存在，无法建立通道')
        }

        this.ws = new WebSocket(`${location.origin.replace('http','ws')}${this.options.url}`)

        this.onopen(this.options.onopen as Callback)
        this.onclose(this.options.onclose as Callback)
        this.onmessage(this.options.onmessage as MessageCallback<RT>)
    }

    /**
     * 自定义发送消息事件
     * @param {String} data 发送的文本
     */
    async send(data: T | string): Promise<void> {

        if (this.ws.readyState !== this.ws.OPEN) {
            //重新连接
            this.destroy()
            this.create()
            while (this.ws.readyState == this.ws.CONNECTING){
                await delay(200)
            }
        }
        if (this.ws.readyState !== this.ws.OPEN) {
            ElMessage.error("没有连接到服务器，无法推送");
            return
        }

        data = JSON.stringify(data)

        this.ws.send(data)
    }

    /**
     * 销毁
     */
    destroy(): void {
        super.reset()

        // 清除重连定时器
        clearTimeout(this.reconnectTimer)

        this.options.isRestory = true

        if (this.ws.readyState !== this.ws.CLOSED&&this.ws.readyState !== this.ws.CLOSING) {
            this.ws.close()
        }
    }

    /**
     * 自定义连接成功事件
     * 如果callback存在，调用callback，不存在调用OPTIONS中的回调
     * @param {Function} callback 回调函数
     */
    onopen(callback: Callback): void {
        this.ws.onopen = event => {
            // 清除重连定时器
            clearTimeout(this.reconnectTimer)

            // 计数器重置
            this.options.reconnectCount = this.reconnectCount

            // 建立心跳机制
            if (this.options.isHeart) {
                super.reset()
                super.start(() => {
                    this.send(this.options.heartMsg as string)
                })
            }

            if (typeof callback === 'function') {
                callback(event)
            } else {
                typeof this.options.onopen === 'function' && this.options.onopen(event)
            }
        }
    }

    /**
     * 自定义关闭事件
     * 如果callback存在，调用callback，不存在调用OPTIONS中的回调
     * @param {Function} callback 回调函数
     */
    onclose(callback: Callback): void {
        this.ws.onclose = event => {
            super.reset()

            if (!this.options.isRestory || this.options.isReconnect) {
                this.onreconnect()
            }

            if (typeof callback === 'function') {
                callback(event)
            } else {
                typeof this.options.onclose === 'function' && this.options.onclose(event)
            }
        }
    }

    /**
     * 自定义错误事件
     * 如果callback存在，调用callback，不存在调用OPTIONS中的回调
     * @param {Function} callback 回调函数
     */
    onerror(callback: Callback): void {
        this.ws.onerror = event => {
            if (typeof callback === 'function') {
                callback(event)
            } else {
                typeof this.options.onerror === 'function' && this.options.onerror(event)
            }
        }
    }

    /**
     * 自定义消息监听事件
     * 如果callback存在，调用callback，不存在调用OPTIONS中的回调
     * @param {Function} callback 回调函数
     */
    onmessage(callback: MessageCallback<RT>): void {

        this.ws.onmessage = (event: MessageEvent<string>) => {
            const strMessage = event.data
            const { code, data, msg }: any = JSON.parse(strMessage)

            if (code === 200) {
                // 收到任何消息，重新开始倒计时心跳检测
                if (this.options.isHeart) {
                    super.reset()
                    super.start(() => {
                        this.send(this.options.heartMsg as string)
                    })
                }

                if (typeof callback === 'function') {
                    callback(data)
                } else {
                    typeof this.options.onmessage === 'function' && this.options.onmessage(data)
                }
            } else {
                ElMessage.error(msg || '收到失败的数据！')
            }
        }
    }

    /**
     * 连接事件
     */
    private onreconnect(): void {
        if ((this.options.reconnectCount as number) > 0 || this.options.reconnectCount === -1) {
            this.reconnectTimer = setTimeout(() => {
                this.create()

                if (this.options.reconnectCount !== -1) (this.options.reconnectCount as number)--
            }, this.options.reconnectTime)
        } else {
            clearTimeout(this.reconnectTimer)

            this.options.reconnectCount = this.reconnectCount
        }
    }
}
